//
//  BCResourceDownloader.m
//  断点上传和下载
//
//  Created by aaaa on 2020/5/13.
//  Copyright © 2020 aaaa. All rights reserved.
//

#import "BCResourceDownloader.h"
#import "BCResourceItem.h"
#import "BCResourceDownloadHelper.h"
#import "BCDownloadConfig.h"
#import <pthread.h>

static NSString *const BCBackGroudDownloadIdentifier = @"BCBackGroudDownloadIdentifier";

#define LOCK(...) pthread_mutex_lock(&_lock); \
__VA_ARGS__; \
pthread_mutex_unlock(&_lock);

#define SYNC_ON_MAIN_QUEUE(...) dispatch_sync(dispatch_get_main_queue(), ^{\
__VA_ARGS__; \
});

@interface BCResourceDownloader()<NSURLSessionDownloadDelegate,NSURLSessionDelegate,NSURLSessionTaskDelegate>
//开始下载/下载中/暂停中的任务
@property(nonatomic,strong)NSMutableDictionary<NSString *,BCResourceItem *> *downloadingTask;
//挂起中的任务
@property(nonatomic,strong)NSMutableDictionary<NSString *,BCResourceItem *> *suspendDownloadTask;
//
@property(nonatomic,strong)NSMutableDictionary<NSString *,BCResourceItem *> *uploadTask;

@end
@implementation BCResourceDownloader
{
    pthread_mutex_t _lock;
}

static BCResourceDownloader *downloader = nil;
+(instancetype)shareDownloader{
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        downloader = [BCResourceDownloader new];
    });
    return downloader;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        //提前初始化 后面避免用点语法
        _downloadingTask = @{}.mutableCopy;
        _uploadTask = @{}.mutableCopy;
        _suspendDownloadTask = @{}.mutableCopy;
        [self initSession];
        NSArray *tasks = [self checkUnFinishDownloadTask];
        NSArray<BCResourceItem *> *items = [BCResourceItem getUnfinishedResourceItemInCacheWithTask:tasks];
        for (BCResourceItem *item in items) {
            item.downloadState = BCDownloadState_Suspending;
            [self addDownloadTaskItem:item];
        }
        NSLog(@"items count = %d",(int)items.count);
    }
    return self;
}

-(void)initSession{
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:BCBackGroudDownloadIdentifier];
    configuration.sessionSendsLaunchEvents = YES;
    configuration.requestCachePolicy = NSURLRequestReturnCacheDataDontLoad;
    configuration.timeoutIntervalForRequest = 3600;
    configuration.timeoutIntervalForResource = 3600;
    configuration.allowsCellularAccess = [BCDownloadConfig allowInWLAN];
    _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
}

-(NSArray *)checkUnFinishDownloadTask{
    __block NSArray *tasks = nil;
    pthread_mutex_lock(&_lock);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        tasks = downloadTasks;
    }];
    pthread_mutex_unlock(&_lock);
    return tasks;
}


#pragma mark - set config -
/// 设置最大下载数量
/// @param maxDownloadNum 最大不超过16
-(void)setMaxDownloadNum:(uint16_t)maxDownloadNum{
    [BCDownloadConfig setMaxDownloadCount:maxDownloadNum];
}

/// 设置允许蜂窝下载
/// @param allowInWLAN default is YES
-(void)setAllowDownloadInWLAN:(BOOL)allowInWLAN{
    [BCDownloadConfig setAllowInWLAN:allowInWLAN];
}

/// 设置允许各种环境都能下载的大小
/// @param byteLength default is MaxFloat
-(void)setAllowDownloadTaskSize:(long)byteLength{
    [BCDownloadConfig setAllowDownloadTaskMaxSize:byteLength];
}

#pragma mark - download -
/// 下载
/// @param url url
/// @param allowContinue 是否支持断点下载
/// @param progress 进度回调
/// @param result 下载结果
-(BCResourceItem *)downloadResourceWithUrl:(NSString *)url
                             allowContinue:(BOOL)allowContinue
                                  progress:(BCResouceProgress __nullable)progress
                                    result:(BCResouceResult __nullable)result{
    //checkUrl
    if (url.length == 0){
        NSAssert(url.length == 0, @"url不可用");
        !result ? : result(NO,nil,@"下载失败");
        return nil;
    }
    pthread_mutex_lock(&_lock);
    BCResourceItem *item = [self getDownloadTaskItemWithUrl:url];
    if (!item) {
        item = [[BCResourceItem alloc] initWithUrl:url];
        [self addDownloadTaskItem:item];
        [item saveDownloadInfo];
    }
    if (progress) item.progressCallBack = progress;
    if (result) item.resultCallBack = result;
    pthread_mutex_unlock(&_lock);
    return item;
}

/* ----- 这里的暂停和挂起 有轻微的区别 挂起代表’被打断性‘的暂停，比如正在执行一个需要良好网络环境的操作 需要暂时性的把正在下载的任务中断，操作完成后要继续 ------ */
// -----------------------------------
-(void)startDownloadWithUrl:(NSString *)url{
    [self continuteDownloadWithUrl:url];
}

//暂停某个下载
-(void)pauseDownloadWithUrl:(NSString *)url{
    pthread_mutex_lock(&_lock);
    BCResourceItem *item = [self getDownloadTaskItemWithUrl:url];
    if (item) {
        [item suspendTask];
    }
    pthread_mutex_unlock(&_lock);
}

//暂停所有下载
-(void)pauseAllDownloadTask{
    pthread_mutex_lock(&_lock);
    if (_downloadingTask.allValues.count == 0) return;
    [_downloadingTask.allValues enumerateObjectsUsingBlock:^(BCResourceItem * _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop) {
        if (item.downloadState == BCDownloadState_Downloading &&
            item.downloadTask.state == NSURLSessionTaskStateRunning)
        {
            [item suspendTask];
        }
    }];
    pthread_mutex_unlock(&_lock);
}

//继续某个下载
-(void)continuteDownloadWithUrl:(NSString *)url{
    pthread_mutex_lock(&_lock);
    BCResourceItem *item = [self getDownloadTaskItemWithUrl:url];
    if (item) {
        [item resumeTask];
    }
    pthread_mutex_unlock(&_lock);
}

//继续全部下载
-(void)continuteAllDownloadTask{
    pthread_mutex_lock(&_lock);
    if (_downloadingTask.allValues.count == 0) return;
    [_downloadingTask.allValues enumerateObjectsUsingBlock:^(BCResourceItem * _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop) {
        if (item.downloadState == BCDownloadState_Suspending)
        {
            [item resumeTask];
        }
    }];
    pthread_mutex_unlock(&_lock);
}

-(BOOL)existDownloadTaskWithUrl:(NSString *)url{
    return [self getDownloadTaskItemWithUrl:url];
}

// -----------------------------------
//挂起所有下载中的任务
-(void)suspendAllDownlondingTask{
    pthread_mutex_lock(&_lock);
    if (_downloadingTask.allValues.count == 0) return;
    __weak typeof(self) weakSelf = self;
    [_downloadingTask.allKeys enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL * _Nonnull stop) {
        BCResourceItem *item = [self.downloadingTask objectForKey:key];
        if (item &&
            item.downloadState == BCDownloadState_Downloading)
        {
            [weakSelf.suspendDownloadTask setObject:item forKey:key];
            [item suspendTask];
        }
    }];
    pthread_mutex_unlock(&_lock);
}

//继续被挂起的任务
-(void)continuteAllSuspendTask{
    pthread_mutex_lock(&_lock);
    if (_suspendDownloadTask.allKeys.count == 0) return;
    [_suspendDownloadTask.allKeys enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL * _Nonnull stop) {
        BCResourceItem *item = [self.downloadingTask objectForKey:key];
        if (item && item.downloadTask &&
            item.downloadState == BCDownloadState_Suspending &&
            item.downloadTask.state == NSURLSessionTaskStateSuspended)
        {
            [item resumeTask];
        }
    }];
    [_suspendDownloadTask removeAllObjects];
    pthread_mutex_unlock(&_lock);
}

// -----------------------------------
//取消某个下载
-(void)cancleDownloadWithUrl:(NSString *)url{
    pthread_mutex_lock(&_lock);
    BCResourceItem *item = [self getDownloadTaskItemWithUrl:url];
    if (item) {
        [item cancelTask];
    }
    pthread_mutex_unlock(&_lock);
}

//取消所有下载
-(void)cancelAllDownloadTask{
    pthread_mutex_lock(&_lock);
    [_downloadingTask.allValues enumerateObjectsUsingBlock:^(BCResourceItem * _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop) {
        [item cancelTask];
    }];
    pthread_mutex_unlock(&_lock);
}

#pragma mark - download delegate -
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{
    !_completionHandler ? : _completionHandler();
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                           didCompleteWithError:(nullable NSError *)error
{
    pthread_mutex_lock(&_lock);
    NSData *resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
    BCResourceItem *item = [self getDownloadTaskItemWithUrl:task.currentRequest.URL.absoluteString];
    if (resumeData) {
        if (item.downloadState == BCDownloadState_Suspending) {
            //暂停下载
            NSLog(@"取消或者暂停下载");
            item.resumeData = resumeData;
            [item saveDownloadInfo];
        }else if(item.downloadState == BCDownloadState_Downloading || item.downloadState == BCDownloadState_unDownload){
            NSLog(@"下载中断");
            if (resumeData) {
                item.resumeData = resumeData;
            }
            [item saveDownloadInfo];
        }else if (item.downloadState == BCDownloadState_Failed){
            [item finishTask];
            item.resultCallBack(NO, nil, @"取消下载");
        }
    }else{
        //下载成功或失败
        NSLog(@"下载成功或失败");
        [item failTask];
        [self removeDownloadTaskItemWithUrl:item.url];
    }
    pthread_mutex_unlock(&_lock);
}

/* Sent when a download task that has completed a download.  The delegate should
 * copy or move the file at the given location to a new location as it will be
 * removed when the delegate message returns. URLSession:task:didCompleteWithError: will
 * still be called.
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
                              didFinishDownloadingToURL:(NSURL *)location
{
     pthread_mutex_lock(&_lock);
     NSString *url = downloadTask.currentRequest.URL.absoluteString;
     BCResourceItem *item = [self getDownloadTaskItemWithUrl:url];
     item.cachePath = [location.absoluteURL path];
     if (item.downloadState == BCDownloadState_Downloading) {
         [item finishTask];
         SYNC_ON_MAIN_QUEUE(!item.resultCallBack ? : item.resultCallBack(YES,nil,@""));
     }
     pthread_mutex_unlock(&_lock);
}

/* Sent periodically to notify the delegate of download progress. */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
                                           didWriteData:(int64_t)bytesWritten
                                      totalBytesWritten:(int64_t)totalBytesWritten
                              totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
    pthread_mutex_lock(&_lock);
    NSLog(@"didWriteData - %lld - %lld",totalBytesWritten,totalBytesExpectedToWrite);
    NSString *url = downloadTask.currentRequest.URL.absoluteString;
    BCResourceItem *item = [self getDownloadTaskItemWithUrl:url];
    item.downloadState = BCDownloadState_Downloading;
    item.recieveLength = totalBytesWritten;
    item.totalLength = totalBytesExpectedToWrite;
    float progress = (float)totalBytesWritten/totalBytesExpectedToWrite;
    SYNC_ON_MAIN_QUEUE(!item.progressCallBack ? : item.progressCallBack(totalBytesWritten,totalBytesExpectedToWrite,progress))
    pthread_mutex_unlock(&_lock);
}

/* Sent when a download has been resumed. If a download failed with an
 * error, the -userInfo dictionary of the error will contain an
 * NSURLSessionDownloadTaskResumeData key, whose value is the resume
 * data.
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
                                      didResumeAtOffset:(int64_t)fileOffset
                                     expectedTotalBytes:(int64_t)expectedTotalBytes {
    pthread_mutex_lock(&_lock);
    NSLog(@"didResumeAtOffset - %lld - %lld",fileOffset,expectedTotalBytes);
    NSString *url = downloadTask.currentRequest.URL.absoluteString;
    BCResourceItem *item = [self getDownloadTaskItemWithUrl:url];
    float progress = (float)fileOffset/expectedTotalBytes;
    SYNC_ON_MAIN_QUEUE(!item.progressCallBack ? : item.progressCallBack(fileOffset,expectedTotalBytes,progress);)
    pthread_mutex_unlock(&_lock);
}

#pragma mark - upload -
/// 上传
/// @param url url
/// @param filePath 文件路径
/// @param allowBackground 是否支持后台下载
/// @param progress 进度回调
/// @param result 结果回调
-(void)uploadResourceWithUrl:(NSString *)url
                    filePath:(NSString *)filePath
             allowBackground:(BOOL)allowBackground
                    progress:(BCResouceProgress)progress
                      result:(BCResouceResult)result{
}

/// 分片上传
/// @param url url
/// @param filePath 文件路径
/// @param allowBackground 是否支持后台
/// @param progress 进度回调
/// @param result 结果回调
-(void)uploadResourceBySeperateDataWithUrl:(NSString *)url
                                  filePath:(NSString *)filePath
                           allowBackground:(BOOL)allowBackground
                                  progress:(BCResouceProgress)progress
                                    result:(BCResouceResult)result{
}

#pragma mark - manage tasks -
-(BCResourceItem *)getDownloadTaskItemWithUrl:(NSString *)url{
    return [_downloadingTask objectForKey:url.lastPathComponent];
}

-(void)addDownloadTaskItem:(BCResourceItem *)item{
    [_downloadingTask setObject:item forKey:item.url.lastPathComponent];
}

-(void)removeDownloadTaskItemWithUrl:(NSString *)url{
    [_downloadingTask removeObjectForKey:url.lastPathComponent];
}

#pragma mark - getter -
-(NSMutableDictionary<NSString *,BCResourceItem *> *)downloadingTask{
    if (!_downloadingTask) {
        _downloadingTask = @{}.mutableCopy;
    }
    return _downloadingTask;
}

-(NSMutableDictionary<NSString *,BCResourceItem *> *)suspendDownloadTask{
    if (!_suspendDownloadTask) {
        _suspendDownloadTask = @{}.mutableCopy;
    }
    return _suspendDownloadTask;
}

-(NSMutableDictionary<NSString *,BCResourceItem *> *)uploadTask{
    if (!_uploadTask) {
        _uploadTask = @{}.mutableCopy;
    }
    return _uploadTask;
}
@end

